package com.zmx.common.common.service;

import com.google.common.base.Preconditions;
import com.zmx.common.common.dto.BaseDto;
import com.zmx.common.common.entitys.BaseEntity;
import com.zmx.common.common.exception.BusinessException;
import com.zmx.common.common.invocation.InvocationInfoProxy;
import com.zmx.common.common.repository.BaseRepository;
import com.zmx.common.common.service.mapper.BaseMapper;
import com.zmx.common.common.utils.RedisExpireRandomUtil;
import com.zmx.common.common.utils.Utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Description: Service层抽象基类
 * @ClassName: BaseService
 * @Author zhaomxr
 * @Date 2021-09-29 09:20
 */
//***数据传输自动转换有两种方式，1.基于BaseMapper的Mapstract方便且性能极高。2.基于TransformMapper的Jackson转换，可进行深度转换
//public class BaseService<TEntity extends BaseEntity, TDto extends BaseDto, TRepository extends BaseRepository<TEntity>, TMapper extends BaseMapper> extends TransformMapper<TEntity, TDto> {
public abstract class BaseService<TEntity extends BaseEntity, TDto extends BaseDto, TRepository extends BaseRepository<TEntity>, TMapper extends BaseMapper<TEntity, TDto>> {

    @Value("${check.onlyNumber.timeout:5000}")
    private int onlyNumber_timeout;

    @Autowired
    protected TRepository repository;
    @Autowired
    protected TMapper mapper;
    @Autowired
    private RedisTemplate redisTemplate;

    private int expire = RedisExpireRandomUtil.expire();

    protected void setMapper(TMapper mapper) {
        this.mapper = mapper;
    }

    public void BaseService(TRepository tRepository) {
        this.repository = tRepository;
    }

    public TDto create(TDto dto) {
        Assert.notNull(dto, "dto 参数不能为空！");
        TEntity entity = this.dtoToEntity(dto);
        if(dto.getCreateTime() == null){
            entity.setCreateTime(new Date());
        }
        if(dto.getModifyTime() == null){
            entity.setModifyTime(new Date());
        }
        entity.setTs(new Date());
        if (StringUtils.isEmpty(entity.getCreator())) {
            entity.setCreator(InvocationInfoProxy.getUserName());
        }
        TEntity saveEntity = repository.save(entity);
        return this.entityToDto(saveEntity);
    }

    public TDto update(TDto dto) {
        Assert.notNull(dto, "dto 参数不能为空！");
        Assert.notNull(dto.getId(), "dto || id 参数不能为空！");
        TEntity entity = this.dtoToEntity(dto);
        if(dto.getModifyTime() == null){
            entity.setModifyTime(new Date());
        }
        entity.setTs(new Date());
        if (StringUtils.isEmpty(entity.getModifier())) {
            entity.setModifier(InvocationInfoProxy.getUserName());
        }
        TEntity saveEntity = repository.save(entity);
        return this.entityToDto(saveEntity);
    }

    @Transactional(rollbackFor = Exception.class)
    public TDto delete(Long id) {
        Assert.notNull(id, "id 参数不能为空！");
        TEntity entity = this.findById(id);
        if (entity == null) {
            return null;
        }
        entity.setModifyTime(new Date());
        entity.setTs(new Date());
        if (StringUtils.isEmpty(entity.getModifier())) {
            entity.setModifier(InvocationInfoProxy.getUserName());
        }
        entity.setDr(1);
        return entityToDto(repository.save(entity));
    }

    @Transactional(rollbackFor = Exception.class)
    public List<TDto> deleteBatch(Long[] ids) {
        Assert.notNull(ids, "ids 参数不能为空！");
        List<TEntity> entityList = this.findByIdIn(ids);
        if (CollectionUtils.isEmpty(entityList)) {
            return null;
        }

        List<TDto> dtoList = new ArrayList<TDto>(entityList.size());
        for (TEntity entity : entityList) {
            TDto dto = delete(entity.getId());
            dtoList.add(dto);
        }
        return dtoList;
    }

    public TEntity findById(Long id) {
        return repository.findById(id);
    }

    public List<TEntity> findByIdIn(Long[] ids) {
        return repository.findByIdIn(ids);
    }

    public Page<TDto> getAll(Map<String, Object> searchParams, int currentPage, int pageSize, boolean sort) {
        //如果传入值是驼峰，转为下划线
        searchParams = Utils.humpToLine(searchParams);

        Pageable pageable = null;
        if (sort) {
            pageable = PageRequest.of(currentPage - 1, pageSize, sort(searchParams));
        } else {
            pageable = PageRequest.of(currentPage - 1, pageSize);
        }

        Page<TEntity> entities = this.getAll(searchParams, pageable, false);
        return new PageImpl<>(entityToDto(entities.getContent()), entities.getPageable(), entities.getTotalElements());
    }

    public Page<TDto> getAll(Map<String, Object> searchParams, int currentPage, int pageSize, boolean sort, boolean distinct) {
        //如果传入值是驼峰，转为下划线
        searchParams = Utils.humpToLine(searchParams);

        Pageable pageable = null;
        if (sort) {
            pageable = PageRequest.of(currentPage - 1, pageSize, sort(searchParams));
        } else {
            pageable = PageRequest.of(currentPage - 1, pageSize);
        }

        Page<TEntity> entities = this.getAll(searchParams, pageable, distinct);
        return new PageImpl<>(entityToDto(entities.getContent()), entities.getPageable(), entities.getTotalElements());
    }

    public Page<TDto> getAll(Map<String, Object> searchParams, int currentPage, int pageSize) {
        return this.getAll(searchParams, currentPage, pageSize, false);
    }

    private Page<TEntity> getAll(Map<String, Object> searchParams, Pageable pageable, boolean distinct) {
        Specification<TEntity> specification = Specification(searchParams, distinct);
        return repository.findAll(specification, pageable);
    }

    public List<TDto> getAll(Map<String, Object> searchParams, boolean sort) {
        //如果传入值是驼峰，转为下划线
        searchParams = Utils.humpToLine(searchParams);

        Specification<TEntity> specification = Specification(searchParams, false);
        List<TEntity> all = null;
        if (sort) {
            all = repository.findAll(specification, sort(searchParams));
        } else {
            all = repository.findAll(specification);
        }

        return entityToDto(all);
    }

    public List<TDto> getAll(Map<String, Object> searchParams, boolean sort, boolean distinct) {
        //如果传入值是驼峰，转为下划线
        searchParams = Utils.humpToLine(searchParams);

        Specification<TEntity> specification = Specification(searchParams, distinct);
        List<TEntity> all = null;
        if (sort) {
            all = repository.findAll(specification, sort(searchParams));
        } else {
            all = repository.findAll(specification);
        }

        return entityToDto(all);
    }

    public List<TDto> getAll(Map<String, Object> searchParams) {
        return this.getAll(searchParams, false);
    }

    public Specification<TEntity> Specification(Map<String, Object> searchParams, boolean distinct) {

        Specification<TEntity> specification = (Specification<TEntity>) (root, query, criteriaBuilder) -> {
            List<Predicate> list = new ArrayList<>();

            if (!CollectionUtils.isEmpty(searchParams)) {
                for (String key : searchParams.keySet()) {
                    Object value = searchParams.get(key);
                    //and
                    if (key.indexOf("_") < 0) {
                        if (value != null) {
                            Predicate predicate = criteriaBuilder.equal(root.get(key), value);
                            list.add(predicate);
                        }
                    }
                    //in 多值查询
                    if (key.endsWith("_in")) {
                        if (value != null) {
                            Path<String> path = root.get(key.replace("_in", ""));
                            CriteriaBuilder.In<Object> in = criteriaBuilder.in(path);

                            List values = (List) value;
                            for (int i = 0; i < values.size(); i++) {
                                in.value(values.get(i));
                            }
                            Predicate predicate = criteriaBuilder.and(criteriaBuilder.and(in));
                            list.add(predicate);
                        }
                    }
                    //> 大于
                    if (key.endsWith("_le")) {
                        if (value != null) {
                            Predicate predicate = criteriaBuilder.le(root.get(key.replace("_le", "")), (Integer) value);
                            list.add(predicate);
                        }
                    }
                    //< 小于
                    if (key.endsWith("_ge")) {
                        if (value != null) {
                            Predicate predicate = criteriaBuilder.le(root.get(key.replace("_ge", "")), (Integer) value);
                            list.add(predicate);
                        }
                    }
                    //>= 大于等于
                    if (key.endsWith("_lt")) {
                        if (value != null) {
                            Predicate predicate = criteriaBuilder.le(root.get(key.replace("_lt", "")), (Integer) value);
                            list.add(predicate);
                        }
                    }
                    //=< 小于等于
                    if (key.endsWith("_gt")) {
                        if (value != null) {
                            Predicate predicate = criteriaBuilder.le(root.get(key.replace("_gt", "")), (Integer) value);
                            list.add(predicate);
                        }
                    }
                    //like 模糊查询
                    if (key.endsWith("_like")) {
                        if (value != null) {
                            Predicate predicate = criteriaBuilder.like(root.get(key.replace("_like", "")), "%" + value + "%");
                            list.add(predicate);
                        }
                    }
                    //between 范围查询
                    if (key.endsWith("_between")) {
                        if (value != null) {
                            List<Integer> betweens = (List<Integer>) value;
                            Predicate predicate = criteriaBuilder.between(root.get(key.replace("_between", "")), betweens.get(0), betweens.get(1));
                            list.add(predicate);
                        }
                    }
                    //or
                    if (key.endsWith("_or")) {
                        if (value != null) {
                            if (list.size() > 0) {
                                Predicate predicateOr = criteriaBuilder.and(list.toArray(new Predicate[0]));
                                Predicate predicate = criteriaBuilder.or(criteriaBuilder.equal(root.get(key.replace("_or", "")), value), predicateOr);
                                //因为是or语句，所以先清除当前list，再将当前语句放入lsit中
                                list.clear();
                                list.add(predicate);
                            }
                        }
                    }
                }

                //取第一个_groupBy参数进行分组
                List<String> groupByList = searchParams.keySet().stream().filter(key -> {
                    return key.endsWith("_groupBy");
                }).collect(Collectors.toList());
                if (groupByList != null && groupByList.size() > 0) {
                    String groupBy = groupByList.get(0);
                    Object obj = searchParams.get(groupBy);

                    //多个group 字段，按照数组顺序依次分组
                    if (obj != null && obj instanceof List) {
                        List<String> fields = (List<String>) obj;
                        if (fields != null && fields.size() > 0) {
                            List<Expression<?>> rootFields = new ArrayList<Expression<?>>(fields.size());
                            for (String field : fields) {
                                rootFields.add(root.get(field));
                            }
                            query.groupBy(rootFields);
                        }
                    }
                }
            }

            query.distinct(distinct);
            return criteriaBuilder.and(list.toArray(new Predicate[0]));
        };
        return specification;
    }

    public Sort sort(Map<String, Object> searchParams) {
        List<Sort.Order> orders = new ArrayList<Sort.Order>();
        for (String key : searchParams.keySet()) {
            if (key.endsWith("_sort")) {
                Object value = searchParams.get(key);
                Sort.Direction direction = null;
                if (Objects.equals(value, "asc")) {
                    direction = Sort.Direction.ASC;
                } else {
                    direction = Sort.Direction.DESC;
                }
                orders.add(new Sort.Order(direction, key.replace("_sort", "")));
            }
        }
        Sort sort = Sort.by(orders);

        return sort;
    }

    public TDto entityToDto(TEntity entity) {
        if (entity == null) {
            return null;
        }
        return (TDto) mapper.entityToDto(entity);
    }

    public List<TDto> entityToDto(List<TEntity> entitys) {
        if (CollectionUtils.isEmpty(entitys)) {
            return null;
        }
        return (List<TDto>) mapper.entityToDtos(entitys);
    }

    public TEntity dtoToEntity(TDto dto) {
        if (dto == null) {
            return null;
        }
        return (TEntity) mapper.dtoToEntity(dto);
    }

    public List<TEntity> dtoToEntitys(List<TDto> dtos) {
        if (CollectionUtils.isEmpty(dtos)) {
            return null;
        }
        return (List<TEntity>) mapper.dtoToEntitys(dtos);
    }

    public void check(String onlyNumber) {
        Preconditions.checkArgument(!StringUtils.isEmpty(onlyNumber), "onlyNumber is null");

        /**
         * 利用redis原子增保证唯一性,超过5秒自动释放
         */
        Long increment = redisTemplate.opsForValue().increment(onlyNumber, 1);
        redisTemplate.expire(onlyNumber, onlyNumber_timeout, TimeUnit.MILLISECONDS);
        if (increment > 1) {
            throw new BusinessException("请勿重复提交! onlyNumber" + onlyNumber);
        }
    }

    public void release(String onlyNumber) {
        redisTemplate.delete(onlyNumber);
    }


    /**
     * 检查ts时间是否在此之后被改动，用于并发控制中 版本检查
     */
    public boolean tsChange(Long id, Date ts) {
        int count = repository.countByIdAndTs(id, ts);
        return count == 0;
    }

}
